/*
 *	TSimpleFormatConversionProvider.java
 *
 *	This file is part of Tritonus: http://www.tritonus.org/
 */

/*
 *  Copyright (c) 1999 - 2004 by Matthias Pfisterer
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU Library General Public License as published
 *   by the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU Library General Public License for more details.
 *
 *   You should have received a copy of the GNU Library General Public
 *   License along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

/*
|<---            this code is formatted to fit into 80 columns             --->|
*/

package org.tritonus.share.sampled.convert;

import java.util.Collection;
import java.util.Iterator;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;

import org.tritonus.share.sampled.AudioFormats;
import org.tritonus.share.ArraySet;
import org.tritonus.share.TDebug;

/**
 * This is a base class for FormatConversionProviders that can convert from each source encoding/format to each target encoding/format. If this is not the case, use TEncodingFormatConversionProvider.
 *
 * <p>
 * Overriding classes must provide a constructor that calls the protected constructor of this class and override
 * <code>AudioInputStream getAudioInputStream(AudioFormat targetFormat, AudioInputStream sourceStream)</code>. The latter method should be able to handle the case that all fields are NOT_SPECIFIED and
 * provide appropriate default values.
 *
 * @author Matthias Pfisterer
 */

// todo:
// - declare a constant ALL_BUT_SAME_VALUE (==-2) or so that can be used in format lists
// - consistent implementation of replacing NOT_SPECIFIED when not given in conversion

public abstract class TSimpleFormatConversionProvider extends TFormatConversionProvider {
	private Collection<AudioFormat.Encoding> m_sourceEncodings;
	private Collection<AudioFormat.Encoding> m_targetEncodings;
	private Collection<AudioFormat> m_sourceFormats;
	private Collection<AudioFormat> m_targetFormats;

	protected TSimpleFormatConversionProvider(Collection<AudioFormat> sourceFormats, Collection<AudioFormat> targetFormats) {
		m_sourceEncodings = new ArraySet<AudioFormat.Encoding>();
		m_targetEncodings = new ArraySet<AudioFormat.Encoding>();
		if (sourceFormats == null) {
			sourceFormats = new ArraySet<AudioFormat>();
		}
		if (targetFormats == null) {
			targetFormats = new ArraySet<AudioFormat>();
		}
		m_sourceFormats = sourceFormats;
		m_targetFormats = targetFormats;
		collectEncodings(m_sourceFormats, m_sourceEncodings);
		collectEncodings(m_targetFormats, m_targetEncodings);
	}

	/**
	 * Disables this FormatConversionProvider. This may be useful when e.g. native libraries are not present. TODO: enable method, better implementation
	 */
	protected void disable() {
		if (TDebug.TraceAudioConverter) {
			TDebug.out("TSimpleFormatConversionProvider.disable(): disabling " + getClass().getName());
		}
		m_sourceEncodings = new ArraySet<AudioFormat.Encoding>();
		m_targetEncodings = new ArraySet<AudioFormat.Encoding>();
		m_sourceFormats = new ArraySet<AudioFormat>();
		m_targetFormats = new ArraySet<AudioFormat>();
	}

	private static void collectEncodings(Collection<AudioFormat> formats, Collection<AudioFormat.Encoding> encodings) {
		Iterator<AudioFormat> iterator = formats.iterator();
		while (iterator.hasNext()) {
			AudioFormat format = iterator.next();
			encodings.add(format.getEncoding());
		}
	}

	@Override
	public AudioFormat.Encoding[] getSourceEncodings() {
		return m_sourceEncodings.toArray(EMPTY_ENCODING_ARRAY);
	}

	@Override
	public AudioFormat.Encoding[] getTargetEncodings() {
		return m_targetEncodings.toArray(EMPTY_ENCODING_ARRAY);
	}

	// overwritten of FormatConversionProvider
	@Override
	public boolean isSourceEncodingSupported(AudioFormat.Encoding sourceEncoding) {
		return m_sourceEncodings.contains(sourceEncoding);
	}

	// overwritten of FormatConversionProvider
	@Override
	public boolean isTargetEncodingSupported(AudioFormat.Encoding targetEncoding) {
		return m_targetEncodings.contains(targetEncoding);
	}

	/**
	 * This implementation assumes that the converter can convert from each of its source encodings to each of its target encodings. If this is not the case, the converter has to override this method.
	 */
	@Override
	public AudioFormat.Encoding[] getTargetEncodings(AudioFormat sourceFormat) {
		if (isAllowedSourceFormat(sourceFormat)) {
			return getTargetEncodings();
		} else {
			return EMPTY_ENCODING_ARRAY;
		}
	}

	/**
	 * This implementation assumes that the converter can convert from each of its source formats to each of its target formats. If this is not the case, the converter has to override this method.
	 */
	@Override
	public AudioFormat[] getTargetFormats(AudioFormat.Encoding targetEncoding, AudioFormat sourceFormat) {
		if (isConversionSupported(targetEncoding, sourceFormat)) {
			return m_targetFormats.toArray(EMPTY_FORMAT_ARRAY);
		} else {
			return EMPTY_FORMAT_ARRAY;
		}
	}

	// TODO: check if necessary
	protected boolean isAllowedSourceEncoding(AudioFormat.Encoding sourceEncoding) {
		return m_sourceEncodings.contains(sourceEncoding);
	}

	protected boolean isAllowedTargetEncoding(AudioFormat.Encoding targetEncoding) {
		return m_targetEncodings.contains(targetEncoding);
	}

	protected boolean isAllowedSourceFormat(AudioFormat sourceFormat) {
		Iterator<AudioFormat> iterator = m_sourceFormats.iterator();
		while (iterator.hasNext()) {
			AudioFormat format = iterator.next();
			if (AudioFormats.matches(format, sourceFormat)) {
				return true;
			}
		}
		return false;
	}

	protected boolean isAllowedTargetFormat(AudioFormat targetFormat) {
		Iterator<AudioFormat> iterator = m_targetFormats.iterator();
		while (iterator.hasNext()) {
			AudioFormat format = iterator.next();
			if (AudioFormats.matches(format, targetFormat)) {
				return true;
			}
		}
		return false;
	}

	// $$fb 2000-04-02 added some convenience methods for overriding classes
	protected Collection<AudioFormat.Encoding> getCollectionSourceEncodings() {
		return m_sourceEncodings;
	}

	protected Collection<AudioFormat.Encoding> getCollectionTargetEncodings() {
		return m_targetEncodings;
	}

	protected Collection<AudioFormat> getCollectionSourceFormats() {
		return m_sourceFormats;
	}

	protected Collection<AudioFormat> getCollectionTargetFormats() {
		return m_targetFormats;
	}

	/**
	 * Utility method to check whether these values match, taking into account AudioSystem.NOT_SPECIFIED.
	 * 
	 * @return true if any of the values is AudioSystem.NOT_SPECIFIED or both values have the same value.
	 */
	// $$fb 2000-08-16: moved from TEncodingFormatConversionProvider
	protected static boolean doMatch(int i1, int i2) {
		return i1 == AudioSystem.NOT_SPECIFIED || i2 == AudioSystem.NOT_SPECIFIED || i1 == i2;
	}

	/**
	 * @see #doMatch(int,int)
	 */
	// $$fb 2000-08-16: moved from TEncodingFormatConversionProvider
	protected static boolean doMatch(float f1, float f2) {
		return f1 == AudioSystem.NOT_SPECIFIED || f2 == AudioSystem.NOT_SPECIFIED || Math.abs(f1 - f2) < 1.0e-9;
	}

	/**
	 * Utility method, replaces all occurences of AudioSystem.NOT_SPECIFIED in <code>targetFormat</code> with the corresponding value in <code>sourceFormat</code>. If <code>targetFormat</code> does
	 * not contain any fields with AudioSystem.NOT_SPECIFIED, it is returned unmodified. The endian-ness and encoding remain the same in all cases.
	 * <p>
	 * If any of the fields is AudioSystem.NOT_SPECIFIED in both <code>sourceFormat</code> and <code>targetFormat</code>, it will remain not specified.
	 * <p>
	 * This method uses <code>getFrameSize(...)</code> (see below) to set the new frameSize, if a new AudioFormat instance is created.
	 * <p>
	 * This method isn't used in TSimpleFormatConversionProvider - it is solely there for inheriting classes.
	 */
	// $$fb 2000-08-16: moved from TEncodingFormatConversionProvider
	protected AudioFormat replaceNotSpecified(AudioFormat sourceFormat, AudioFormat targetFormat) {
		boolean bSetSampleSize = false;
		boolean bSetChannels = false;
		boolean bSetSampleRate = false;
		boolean bSetFrameRate = false;
		if (targetFormat.getSampleSizeInBits() == AudioSystem.NOT_SPECIFIED && sourceFormat.getSampleSizeInBits() != AudioSystem.NOT_SPECIFIED) {
			bSetSampleSize = true;
		}
		if (targetFormat.getChannels() == AudioSystem.NOT_SPECIFIED && sourceFormat.getChannels() != AudioSystem.NOT_SPECIFIED) {
			bSetChannels = true;
		}
		if (targetFormat.getSampleRate() == AudioSystem.NOT_SPECIFIED && sourceFormat.getSampleRate() != AudioSystem.NOT_SPECIFIED) {
			bSetSampleRate = true;
		}
		if (targetFormat.getFrameRate() == AudioSystem.NOT_SPECIFIED && sourceFormat.getFrameRate() != AudioSystem.NOT_SPECIFIED) {
			bSetFrameRate = true;
		}
		if (bSetSampleSize || bSetChannels || bSetSampleRate || bSetFrameRate || (targetFormat.getFrameSize() == AudioSystem.NOT_SPECIFIED && sourceFormat.getFrameSize() != AudioSystem.NOT_SPECIFIED)) {
			// create new format in place of the original target format
			float sampleRate = bSetSampleRate ? sourceFormat.getSampleRate() : targetFormat.getSampleRate();
			float frameRate = bSetFrameRate ? sourceFormat.getFrameRate() : targetFormat.getFrameRate();
			int sampleSize = bSetSampleSize ? sourceFormat.getSampleSizeInBits() : targetFormat.getSampleSizeInBits();
			int channels = bSetChannels ? sourceFormat.getChannels() : targetFormat.getChannels();
			int frameSize = getFrameSize(targetFormat.getEncoding(), sampleRate, sampleSize, channels, frameRate, targetFormat.isBigEndian(), targetFormat.getFrameSize());
			targetFormat = new AudioFormat(targetFormat.getEncoding(), sampleRate, sampleSize, channels, frameSize, frameRate, targetFormat.isBigEndian());
		}
		return targetFormat;
	}

	/**
	 * Calculates the frame size for the given format description. The default implementation returns AudioSystem.NOT_SPECIFIED if either <code>sampleSize</code> or <code>channels</code> is
	 * AudioSystem.NOT_SPECIFIED, otherwise <code>sampleSize*channels/8</code> is returned.
	 * <p>
	 * If this does not reflect the way to calculate the right frame size, inheriting classes should overwrite this method if they use replaceNotSpecified(...). It is not used elsewhere in this class.
	 */
	// $$fb 2000-08-16: added
	protected int getFrameSize(AudioFormat.Encoding encoding, float sampleRate, int sampleSize, int channels, float frameRate, boolean bigEndian, int oldFrameSize) {
		if (sampleSize == AudioSystem.NOT_SPECIFIED || channels == AudioSystem.NOT_SPECIFIED) {
			return AudioSystem.NOT_SPECIFIED;
		}
		return ((sampleSize + 7) / 8) * channels;
	}

}

/*** TSimpleFormatConversionProvider.java ***/
